﻿namespace Foggy.Data
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using System.Xml.Linq;

    using Foggy.Data.Users;

    using NLog;

    using RestSharp;

    /// <summary>
    /// FogBugz repository
    /// FogBugz XML API:
    /// http://fogbugz.stackexchange.com/fogbugz-xml-api
    /// </summary>
    public class FogBugzRepository : IFogBugzRepository
    {
        /// <summary>
        /// The logger instance
        /// </summary>
        private static readonly Logger Logger = LogManager.GetCurrentClassLogger();

        /// <summary>
        /// The fogbugz installation URL
        /// </summary>
        private readonly string fogBugzUrl;

        /// <summary>
        /// Initializes a new instance of the <see cref="FogBugzRepository" /> class.
        /// </summary>
        /// <param name="fogbugzUrl">The fogbugz URL.</param>
        public FogBugzRepository(string fogbugzUrl)
        {
            this.fogBugzUrl = fogbugzUrl;
        }

        /// <summary>
        /// Gets or sets the rest client.
        /// </summary>
        /// <value>The rest client.</value>
        protected RestClient RestClient { get; set; }

        /// <summary>
        /// Logs the user on.
        /// </summary>
        /// <param name="email">The email.</param>
        /// <param name="password">The password.</param>
        /// <returns>The token if successful</returns>
        public bool LogOn(string email, string password)
        {
            try
            {
                this.RestClient = new RestClient(this.fogBugzUrl);

                var request = GetRequest();
                request.AddParameter("cmd", "logon");
                request.AddParameter("email", email);
                request.AddParameter("password", password);

                var token = this.GetResponseElement(request).Element("token").Value;
                this.RestClient = new RestClient(this.fogBugzUrl) { Authenticator = new FogBugzAuthenticator(token) };
            }
            catch (Exception exception)
            {
                Logger.ErrorException("There was an erroring logging on", exception);
                return false;
            }
            
            return true;
        }

        /// <summary>
        /// Logs the user off
        /// </summary>
        public void LogOff()
        {
            var request = GetRequest();
            request.AddParameter("cmd", "logoff");

            // TODO: check the response for errors
            var response = this.RestClient.Execute(request);            
        }

        public IDictionary<int, User> GetUsers(IEnumerable<string> userEmails)
        {
            // TODO: this is currently populating all users, and then filtering, instead it should also populate
            // the users in the user email list to start with
            var users = this.GetUsers();
            
            if (users == null)
            {
                return null;
            }

            var includedUsers = from u in users join e in userEmails on u.Value.Email equals e select u.Value;
            return includedUsers.ToDictionary(u => u.Id, u => u);
        }

        public void UpdateUsers(IEnumerable<User> users)
        {
            this.PopulateCurrentBugs(users);
        }

        /// <summary>
        /// Gets the request.
        /// </summary>
        /// <returns>The request</returns>
        private static RestRequest GetRequest()
        {
            return new RestRequest("api.asp", Method.GET);
        }

        /// <summary>
        /// Gets the response element.
        /// </summary>
        /// <param name="request">The request.</param>
        /// <returns>The response element</returns>
        private XElement GetResponseElement(IRestRequest request)
        {
            var response = this.RestClient.Execute(request);

            try
            {

                var xdocument = XDocument.Parse(response.Content);
                
                // TODO: check response for errors
                return xdocument.Element("response");
            }
            catch (Exception exception)
            {
                Logger.ErrorException("There was an error parsing the response", exception);
                return null;
            }
        }

        private int? GetCurrentIssueIdForUser(int userId)
        {
            var request = GetRequest();
            request.AddParameter("cmd", "listPeople");

            var root = this.GetResponseElement(request);

            if (root == null)
            {
                return null;
            }

            var people = root.Element("people");

            if (people == null)
            {
                return null;
            }

            var userBugs =
                people.Elements("person").Select(
                    pe =>
                    new
                        {
                            Id = Convert.ToInt32(pe.Element("ixPerson").Value),
                            CurrentBugId = Convert.ToInt32(pe.Element("ixBugWorkingOn").Value)
                        });

            var currentUser = userBugs.FirstOrDefault(u => u.Id == userId);
            if (currentUser == null || currentUser.CurrentBugId == 0)
            {
                return null;
            }

            return currentUser.CurrentBugId;
        }

        private Issue GetIssueDetails(int issueId)
        {
            var request = GetRequest();
            request.AddParameter("cmd", "search");
            request.AddParameter("q", issueId);
            request.AddParameter("cols", "sTitle,sProject,ixPriority,sPriority,hrsOrigEst,hrsCurrEst,hrsElapsed");

            var response = this.GetResponseElement(request);
            var caseCount = Convert.ToInt32(response.Element("cases").Attribute("count").Value);
            if (caseCount != 1)
            {
                return null;
            }

            var issue = new Issue
            {
                Id = issueId,
            };

            var caseElement =
                response.Element("cases").Elements("case").FirstOrDefault(
                    ce => ce.Attribute("ixBug").Value == issueId.ToString());

            if (caseElement == null)
            {
                return null;
            }

            issue.Project = caseElement.Element("sProject").Value;
            issue.Title = caseElement.Element("sTitle").Value;
            issue.PriorityName = caseElement.Element("sPriority").Value;
            issue.OriginalEstimate = GetTimeSpanFromString(caseElement.Element("hrsOrigEst").Value);
            issue.CurrentEstimate = GetTimeSpanFromString(caseElement.Element("hrsCurrEst").Value);
            issue.ElapsedTime = GetTimeSpanFromString(caseElement.Element("hrsElapsed").Value);

            return issue;
        }

        private void PopulateCurrentBugs(IEnumerable<User> users)
        {
            if (users == null || users.Count() == 0)
            {
                return;
            }

            foreach (var user in users)
            {                
                var issueId = this.GetCurrentIssueIdForUser(user.Id);

                if (!issueId.HasValue)
                {
                    user.CurrentIssue = null;
                    continue;
                }

                user.CurrentIssue = this.GetIssueDetails(issueId.Value);
            }
        }

        private static TimeSpan? GetTimeSpanFromString(string value)
        {            
            if (string.IsNullOrWhiteSpace(value))
            {
                return null;
            }

            double valueAsDouble;
            if (double.TryParse(value, out valueAsDouble))
            {
                return TimeSpan.FromHours(valueAsDouble);
            }

            return null;
        }

        private Dictionary<int, User> GetUsers()
        {
            var request = GetRequest();
            request.AddParameter("cmd", "listPeople");

            var root = this.GetResponseElement(request);

            if (root == null)
            {
                return Enumerable.Empty<User>().ToDictionary(u => u.Id, u => u);
            }

            var people = root.Element("people");

            if (people == null)
            {
                return Enumerable.Empty<User>().ToDictionary(u => u.Id, u => u);
            }

            var users = people.Elements("person").Select(pe => 
                new User
                    {
                        Id = Convert.ToInt32(pe.Element("ixPerson").Value),
                        Email = pe.Element("sEmail").Value,
                        Forename = pe.Element("sFullName").Value.Split(' ').Count() > 0 ? pe.Element("sFullName").Value.Split(' ')[0] : null,
                        Surname = pe.Element("sFullName").Value.Split(' ').Count() > 1 ? pe.Element("sFullName").Value.Split(' ')[1] : null,
                        IsActive = !Convert.ToBoolean(pe.Element("fDeleted").Value),
                        CurrentBugId = Convert.ToInt32(pe.Element("ixBugWorkingOn").Value),
                        IsAdmin = Convert.ToBoolean(pe.Element("fAdministrator").Value)
                    });

            var userDictionary = users.Where(u => u.IsActive).ToDictionary(u => u.Id, u => u);
            return userDictionary;
        }
    }
}